# Copyright (c) OpenMMLab. All rights reserved.
import argparse
import mmcv
import numpy as np
import os
import torch
import warnings
from mmcls.apis import multi_gpu_test, single_gpu_test
from mmcls.datasets import build_dataloader, build_dataset
from mmcls.models import build_classifier
from mmcls.utils import (auto_select_device, get_root_logger,
                         setup_multi_processes, wrap_distributed_model,
                         wrap_non_distributed_model)
from mmcv import DictAction
from mmcv.runner import (get_dist_info, init_dist, load_checkpoint,
                         wrap_fp16_model)
from numbers import Number


#==================================

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Subset, random_split
import matplotlib.patches as patches

from tools.CKA.cka import CKACalculator


# Copyright (c) OpenMMLab. All rights reserved.
import argparse

from pathlib import Path



def parse_args():
    parser = argparse.ArgumentParser(description='mmcls test model')
    parser.add_argument('config', help='test config file path')
    parser.add_argument('config2', help='test config file path')
    parser.add_argument('--checkpoint1', help='checkpoint file')
    parser.add_argument('--checkpoint2', help='checkpoint file')
    parser.add_argument('--out', help='output result file')
    out_options = ['class_scores', 'pred_score', 'pred_label', 'pred_class']
    parser.add_argument(
        '--out-items',
        nargs='+',
        default=['all'],
        choices=out_options + ['none', 'all'],
        help='Besides metrics, what items will be included in the output '
        f'result file. You can choose some of ({", ".join(out_options)}), '
        'or use "all" to include all above, or use "none" to disable all of '
        'above. Defaults to output all.',
        metavar='')
    parser.add_argument(
        '--metrics',
        type=str,
        nargs='+',
        help='evaluation metrics, which depends on the dataset, e.g., '
        '"accuracy", "precision", "recall", "f1_score", "support" for single '
        'label dataset, and "mAP", "CP", "CR", "CF1", "OP", "OR", "OF1" for '
        'multi-label dataset')

    parser.add_argument(
        '--gpu-collect',
        action='store_true',
        help='whether to use gpu to collect results')
    parser.add_argument('--tmpdir', help='tmp dir for writing some results')
    parser.add_argument(
        '--cfg-options',
        nargs='+',
        action=DictAction,
        help='override some settings in the used config, the key-value pair '
        'in xxx=yyy format will be merged into config file. If the value to '
        'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
        'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
        'Note that the quotation marks are necessary and that no white space '
        'is allowed.')
    parser.add_argument(
        '--metric-options',
        nargs='+',
        action=DictAction,
        default={},
        help='custom options for evaluation, the key-value pair in xxx=yyy '
        'format will be parsed as a dict metric_options for dataset.evaluate()'
        ' function.')
    parser.add_argument(
        '--show-options',
        nargs='+',
        action=DictAction,
        help='custom options for show_result. key-value pair in xxx=yyy.'
        'Check available options in `model.show_result`.')
    parser.add_argument(
        '--gpu-ids',
        type=int,
        nargs='+',
        help='(Deprecated, please use --gpu-id) ids of gpus to use '
        '(only applicable to non-distributed testing)')
    parser.add_argument(
        '--gpu-id',
        type=int,
        default=0,
        help='id of gpu to use '
        '(only applicable to non-distributed testing)')
    parser.add_argument(
        '--launcher',
        choices=['none', 'pytorch', 'slurm', 'mpi'],
        default='none',
        help='job launcher')
    parser.add_argument('--local_rank', type=int, default=0)
    parser.add_argument('--device', help='device used for testing')
    parser.add_argument(
        '--output-prediction',
        help='where to save prediction in csv file',
        default=False)
    parser.add_argument('--savepng', type=str, default='ft.png')
    parser.add_argument('--title', type=str, default='')
    parser.add_argument('--dataset', type=str, default='')
    args = parser.parse_args()
    if 'LOCAL_RANK' not in os.environ:
        os.environ['LOCAL_RANK'] = str(args.local_rank)



    return args




def main():
    args = parse_args()
    cfg = mmcv.Config.fromfile(args.config)
    cfg2 = mmcv.Config.fromfile(args.config2)
    if args.cfg_options is not None:
        cfg.merge_from_dict(args.cfg_options)
        cfg2.merge_from_dict(args.cfg_options)
    cfg.output_prediction = args.output_prediction
    cfg2.output_prediction = args.output_prediction
    # set multi-process settings
    setup_multi_processes(cfg)
    setup_multi_processes(cfg2)

    # set cudnn_benchmark
    if cfg.get('cudnn_benchmark', False):
        torch.backends.cudnn.benchmark = True
    cfg.model.pretrained = None
    cfg2.model.pretrained = None
    if args.gpu_ids is not None:
        cfg.gpu_ids = args.gpu_ids[0:1]
        cfg2.gpu_ids = args.gpu_ids[0:1]
        warnings.warn('`--gpu-ids` is deprecated, please use `--gpu-id`. '
                      'Because we only support single GPU mode in '
                      'non-distributed testing. Use the first GPU '
                      'in `gpu_ids` now.')
    else:
        cfg.gpu_ids = [args.gpu_id]
        cfg2.gpu_ids = [args.gpu_id]
    cfg.device = args.device or auto_select_device()
    cfg2.device = args.device or auto_select_device()
    # init distributed env first, since logger depends on the dist info.
    if args.launcher == 'none':
        distributed = False
    else:
        distributed = True
        init_dist(args.launcher, **cfg.dist_params)
        init_dist(args.launcher, **cfg2.dist_params)

    dataset = build_dataset(cfg.data.test, default_args=dict(test_mode=True))

    # build the dataloader
    # The default loader config
    loader_cfg = dict(
        # cfg.gpus will be ignored if distributed
        num_gpus=1 if cfg.device == 'ipu' else len(cfg.gpu_ids),
        dist=distributed,
        round_up=True,
    )
    # The overall dataloader settings
    loader_cfg.update({
        k: v
        for k, v in cfg.data.items() if k not in [
            'train', 'val', 'test', 'train_dataloader', 'val_dataloader',
            'test_dataloader'
        ]
    })
    test_loader_cfg = {
        **loader_cfg,
        'shuffle': False,  # Not shuffle by default
        'sampler_cfg': None,  # Not use sampler by default
        **cfg.data.get('test_dataloader', {}),
    }
    # the extra round_up data will be removed during gpu/cpu collect
    data_loader = build_dataloader(dataset, **test_loader_cfg)

    # build the model and load checkpoint
    model1 = build_classifier(cfg.model)
    model2 = build_classifier(cfg2.model)
    fp16_cfg = cfg.get('fp16', None)
    if fp16_cfg is not None:
        wrap_fp16_model(model1)
        wrap_fp16_model(model2)
    checkpoint1 = load_checkpoint(model1, args.checkpoint1, map_location='cpu')
    checkpoint2 = load_checkpoint(model2, args.checkpoint2, map_location='cpu')

    if 'CLASSES' in checkpoint1.get('meta', {}):
        CLASSES = checkpoint1['meta']['CLASSES']
    else:
        from mmcls.datasets import ImageNet
        warnings.simplefilter('once')
        warnings.warn('Class names are not saved in the checkpoint\'s '
                      'meta data, use imagenet by default.')
        CLASSES = ImageNet.CLASSES

    if not distributed:
        model1 = wrap_non_distributed_model(model1, device=cfg.device, device_ids=cfg.gpu_ids)
        model2 = wrap_non_distributed_model(model2, device=cfg2.device, device_ids=cfg2.gpu_ids)
        if cfg.device == 'ipu':
            from mmcv.device.ipu import cfg2options, ipu_model_wrapper
            opts = cfg2options(cfg.runner.get('options_cfg', {}))
            if fp16_cfg is not None:
                model1.half()
                model2.half()
            model1 = ipu_model_wrapper(model1, opts, fp16_cfg=fp16_cfg)
            model2 = ipu_model_wrapper(model2, opts, fp16_cfg=fp16_cfg)
            data_loader.init(opts['inference'])
        model1.CLASSES = CLASSES
    else:
        model1 = wrap_distributed_model(model1, device=cfg.device, broadcast_buffers=False)
        model2 = wrap_distributed_model(model2, device=cfg2.device, broadcast_buffers=False)


    print('Dummy models created')



    calculator = CKACalculator(model1=model1, model2=model2, dataloader=data_loader,num_epochs=1)

    cka_output = calculator.calculate_cka_matrix()
    print(f"CKA output size: {cka_output.size()}")
    cka_output = cka_output.cpu().numpy()
    vmin = 0  # 最小值
    vmax = 1  # 最大值

    plt.rcParams['figure.figsize'] = (5, 5)
    plt.imshow(cka_output, cmap='inferno', origin='lower', vmin=vmin, vmax=vmax)

    # 在每个像素位置添加数值标签
    for i in range(cka_output.shape[0]):
        for j in range(cka_output.shape[1]):
            plt.text(j, i, "%.2f" % cka_output[i, j], ha='center', va='center', color='white',fontsize=6)

    # 保存为PNG图像文件
    plt.savefig(f'{args.savepng}', dpi=300)
    print('similarity:', cka_output[-1][-1])



if __name__ == "__main__":
    main()